from flask import Flask, request, jsonify
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build
import base64
from email import message_from_bytes
from datetime import datetime, timedelta
import logging
import os
import configparser
import re

# Load configuration file
config = configparser.ConfigParser()
config.read('config.ini')

# Logging configuration
LOG_LEVEL = config['logging'].get('log_level', 'INFO').upper()
LOG_FILE = config['logging'].get('log_file', 'gmail-api-client.log')

logging.basicConfig(level=LOG_LEVEL,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                    handlers=[logging.FileHandler(LOG_FILE)])

logger = logging.getLogger(__name__)

# Flask app initialization
app = Flask(__name__)

# Configuration paths
SERVICE_ACCOUNT_FILE = config['gmail'].get('service_account_file', 'gmail-api-client.json')
SCOPES = [config['gmail'].get('scopes', 'https://www.googleapis.com/auth/gmail.readonly')]

# Initialize credentials
credentials = None

# Check if service account key file exists and load credentials
if not os.path.exists(SERVICE_ACCOUNT_FILE):
    logger.critical(f"Service account key file '{SERVICE_ACCOUNT_FILE}' does not exist.")
else:
    try:
        # Create service account credentials
        credentials = Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
        logger.info(f"Successfully loaded service account key file '{SERVICE_ACCOUNT_FILE}'")
    except Exception as e:
        logger.critical(f"Failed to load service account key file: {e}")
        credentials = None  # Ensure credentials is None if an error occurred


def parse_time_param(time_param, time_type="M"):
    """Parse the time parameter based on the given type (M for minutes, H for hours)"""
    try:
        if isinstance(time_param, int):
            return timedelta(minutes=time_param)
        elif isinstance(time_param, str):
            if time_param.isdigit() and time_type == 'M':  # Handle "72" as a numeric string
                return timedelta(minutes=int(time_param))
            elif time_type == 'H':
                return timedelta(hours=int(time_param))
            else:
                logger.warning(f"Invalid time parameter: {time_param}")
                return None
        else:
            logger.warning(f"Invalid time parameter type: {type(time_param)}")
            return None
    except ValueError as ve:
        logger.error(f"Time parsing failed: {ve}")
        return None


def get_charset(headers):
    """Extract charset from Content-Type header."""
    for header in headers:
        if header['name'].lower() == 'content-type':
            content_type = header['value']
            match = re.search(r'charset=["\']?([\w-]+)["\']?', content_type)
            if match:
                return match.group(1)
    return 'utf-8'  # Default charset if not specified


def decode_content(data, charset='utf-8'):
    """Decode the content using the provided charset, with fallback options."""
    try:
        return base64.urlsafe_b64decode(data.encode('ASCII')).decode(charset)
    except (UnicodeDecodeError, ValueError) as e:
        logger.error(f"Failed to decode content with charset {charset}: {e}")
        logger.debug(f"Attempting to decode with fallback encodings...")
        # Try fallback encodings
        for fallback_charset in ['utf-8', 'gbk', 'ISO-8859-1']:
            try:
                return base64.urlsafe_b64decode(data.encode('ASCII')).decode(fallback_charset)
            except (UnicodeDecodeError, ValueError):
                continue  # Try the next fallback encoding
        # If all decoding attempts fail, log the error and return a placeholder message
        logger.error(f"Failed to decode content with all attempted charsets. Returning undecodable content placeholder.")
        return "[Undecodable content]"


def get_latest_email(user_email, query=None, max_results=1):
    """Fetch the latest email for the specified user based on the query, also returns the total number of matched emails"""
    if not credentials:
        logger.error("Service account credentials not loaded, cannot fetch emails.")
        return None, 0

    try:
        logger.info(f"Fetching latest email for user {user_email}, query: {query or 'No query'}")

        if not user_email or "@" not in user_email:
            logger.error(f"Invalid email address: {user_email}")
            return None, 0

        # Use delegated credentials for the specified user
        delegated_credentials = credentials.with_subject(user_email)
        service = build('gmail', 'v1', credentials=delegated_credentials)

        # Perform the query and get the messages
        results = service.users().messages().list(userId=user_email, q=query, maxResults=max_results).execute()
        messages = results.get('messages', [])

        total_matched_emails = len(messages)

        if not messages:
            logger.info("No matching emails found.")
            return None, total_matched_emails

        # Fetch the most recent email
        msg_id = messages[0]['id']
        msg = service.users().messages().get(userId=user_email, id=msg_id).execute()

        logger.debug(f"Get Message info: {msg_id} --> {msg}")

        # Initialize a variable to store the email content
        email_content = None

        # Check if the payload is multipart
        if msg['payload']['mimeType'].startswith('multipart/'):
            for part in msg['payload'].get('parts', []):
                part_mime_type = part.get('mimeType', '')
                charset = get_charset(part.get('headers', []))
                if part_mime_type == 'text/plain':
                    # Decode text/plain content using the detected charset
                    email_content = decode_content(part['body']['data'], charset)
                    logger.debug(f"Plain text content found in part: {part['partId']} with charset {charset}")
                    break  # Stop if we find the plain text content
                elif part_mime_type == 'text/html':
                    # Optionally handle HTML content if you prefer it over plain text
                    email_content = decode_content(part['body']['data'], charset)
                    logger.debug(f"HTML content found in part: {part['partId']} with charset {charset}")
                    # Continue searching, but you might want to use this content if no plain text is found
        else:
            # Handle non-multipart emails
            charset = get_charset(msg['payload'].get('headers', []))
            if 'data' in msg['payload']['body']:
                email_content = decode_content(msg['payload']['body']['data'], charset)

        if email_content:
            logger.debug(f"Email retrieved successfully, ID: {msg_id}")
            return email_content, total_matched_emails
        else:
            logger.warning(f"Email {msg_id} has no content.")
            return "No content in this email", total_matched_emails

    except Exception as e:
        logger.error(f"Failed to retrieve email: {e}")
        return None, 0  # Return None to indicate failure along with a count of 0


@app.route('/get_latest_email', methods=['POST'])
def get_latest_email_route():
    try:
        data = request.json
        if not data:
            logger.error("No JSON data in request.")
            return jsonify({'status': 'failure', 'message': 'Request data is missing'}), 400

        email = data.get('email')
        keyword = data.get('keyword')
        time_param = data.get('time')
        time_type = data.get('time_type', "M").upper()

        if not email:
            logger.error("Email parameter is missing.")
            return jsonify({'status': 'failure', 'message': 'Email parameter is required'}), 400

        query = ""

        if time_param:
            # Parse time parameter and create query
            time_delta = parse_time_param(time_param, time_type)
            if time_delta is None:
                return jsonify({'status': 'failure', 'message': 'Invalid time format'}), 400

            time_now = datetime.now()
            time_ago = time_now - time_delta
            after_timestamp = int(time_ago.timestamp())
            query += f'after:{after_timestamp} '

        if keyword:
            query += keyword

        if not time_param and not keyword:
            # No time or keyword provided, get the latest email overall
            logger.info("No time or keyword provided, fetching the latest email overall.")
            latest_email, total_matched_emails = get_latest_email(email)
        else:
            logger.info(f"Processing query request, email: {email}, keyword: {keyword}, time_param: {time_param}")
            latest_email, total_matched_emails = get_latest_email(email, query.strip())

        if latest_email:
            logger.info(f"Email content retrieved successfully. Total matched emails: {total_matched_emails}")
            return jsonify({'status': 'success', 'email_content': latest_email, 'total_matched_emails': total_matched_emails})
        else:
            logger.info("No emails found matching the criteria.")
            return jsonify({'status': 'failure', 'message': 'No messages found', 'total_matched_emails': total_matched_emails})

    except Exception as e:
        logger.error(f"Failed to process request: {e}")
        return jsonify({'status': 'failure', 'message': 'An error occurred'}), 500


if __name__ == '__main__':
    try:
        host = config['flask'].get('host', '0.0.0.0')
        port = config['flask'].getint('port', 5000)
        debug = config['flask'].getboolean('debug', True)
        logger.info("Starting Flask application...")
        app.run(host=host, port=port, debug=debug)
    except Exception as e:
        logger.critical(f"Failed to start Flask application: {e}")
